package com.opt.sample;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.apache.log4j.Logger;

public class WorkOnAlgorithm {

	private static Logger LOGGER = Logger.getLogger(WorkOnAlgorithm.class);

	private ExcelManager EM;

	public WorkOnAlgorithm(String filepath) {
		LOGGER.debug("WorkOnAlgorithm started...");
		EM = new ExcelManager(filepath);
		LOGGER.debug("WorkOnAlgorithm finished...");

	}

	private NewTask initTask(int valueOfPackages) {
		LOGGER.debug("initTask started...");
		// LOGGER.debug("initTask valueOfPackages: " + valueOfPackages);
		NewTask task = new NewTask();

		while (task.getCandidates().size() < valueOfPackages) {

			task.addCandidate(EM.getCandidate());
		}
		// LOGGER.debug("initTask task.size(): " + task.getCandidates().size());
		LOGGER.debug("initTask finished...");
		return task;

	}

	public NewPopulation initPopulation(int populationSize, int valueOfPackages) {
		LOGGER.debug("initPopulation Started...");

		NewPopulation population = new NewPopulation();

		while (population.getTasks().size() < populationSize) {

			population.addTasks(this.initTask(valueOfPackages));
		}

		LOGGER.debug("initPopulation finished...");
		return population;

	}

	public BestGene getTheBestGene(NewPopulation population, int valueOfGenes) {

		LOGGER.debug("getTheBestGene started...");
		BestGene bestGene = new BestGene();
		LOGGER.debug("population.getTassk().size()"
				+ population.getTasks().size());
		// for (NewTask task : population.getTasks()) {
		// LOGGER.debug("task przed: " + task.toString());
		// }
		Collections.sort(population.tasks);
		// for (NewTask task : population.getTasks()) {
		// LOGGER.debug("task po: " + task.toString());
		// }
		for (int i = 0; i < valueOfGenes; i++) {
			bestGene.addTasks(population.getTasks().get(i));
		}

		LOGGER.debug("getTheBestGene finished...");
		return bestGene;
	}

	public Double sumGoalFunctions(NewPopulation population) {
		LOGGER.debug("sumGoalFunctions started...");
		double sum = 0;
		for (NewTask task : population.getTasks()) {
			sum += goalFunction(task);
		}
		LOGGER.debug("sumGoalFunctions finished...");
		return sum;

	}

	public static Double goalFunction(NewTask task) {

		LOGGER.debug("goalFunction started...");

		double distanceFunction = limitDistanceFunction(task);
		Double equal = Double.NEGATIVE_INFINITY;

		if (limitPriorityFunction(task) && (distanceFunction > 500)) {

			double prioritiesAndPrifitsSum = 0;
			double alfa = 0.32;

			for (NewPackage newPackage : task.getCandidates()) {

				prioritiesAndPrifitsSum += newPackage.getCandPriority()
						* newPackage.getCandProfit();
			}

			equal = prioritiesAndPrifitsSum - alfa * distanceFunction;

		}

		LOGGER.debug("goalFunction equal: " + equal);

		return equal;

	}

	private static boolean limitPriorityFunction(NewTask task) {

		LOGGER.debug("limitPriorityFunction started...");

		boolean limit = false;

		double prioritiesSum = 0;

		for (NewPackage newPackage : task.getCandidates()) {

			prioritiesSum += newPackage.getCandPriority();
		}

		LOGGER.debug("prioritiesSum: " + prioritiesSum);
		LOGGER.debug("task.getCandidates().size(): "
				+ task.getCandidates().size());
		double averagePriority = prioritiesSum / task.getCandidates().size();

		LOGGER.debug("averagePriority: " + averagePriority);

		if (averagePriority < 3) {
			limit = true;
		}
		LOGGER.debug("limitPriorityFunction limit: " + limit);
		LOGGER.debug("limitPriorityFunction finished...");
		return limit;

	}

	private static double limitDistanceFunction(NewTask task) {
		LOGGER.debug("limitDistanceFunction started...");
		double distance = 0;
		int taskSize = task.getCandidates().size();

		// for (int i = 0; i < task.candidates.size(); i++) {
		// LOGGER.debug("newPackage przed: "
		// + task.candidates.get(i).getCandCity());
		// }

		Collections.sort(task.candidates);

		// for (int i = 0; i < task.candidates.size(); i++) {
		// LOGGER.debug("newPackage po: "
		// + task.candidates.get(i).getCandCity());
		// }

		try {
			distance += Distance.getDistance(0, task.getCandidates().get(0)
					.getCandCity());

			for (int i = 0; i < taskSize; i++) {
				if (i == taskSize - 1) {
					distance += Distance.getDistance(task.getCandidates()
							.get(i).getCandCity(), 0);
				} else {
					distance += Distance.getDistance(task.getCandidates()
							.get(i).getCandCity(),
							task.getCandidates().get(i + 1).getCandCity());
				}
				LOGGER.debug("limitDistanceFunction iteration: " + i
						+ "distance: " + distance);

			}
		} catch (Exception e) {
			LOGGER.error(e);
		}
		LOGGER.debug("limitDistanceFunction distance: " + distance);
		LOGGER.debug("limitDistanceFunction finished...");
		return distance;

	}

	private NewTask crossFunction(NewTask husband, NewTask wife) {

		LOGGER.debug("crossFunction started...");
		NewTask children = new NewTask();
		int taskSize = husband.getCandidates().size();
		Random rand = new Random();

		if (taskSize % 2 == 0) {
			// liczba parzysta

			for (int i = 0; i < taskSize / 2; i++) {

				children.addCandidate(husband.getCandidates().get(
						rand.nextInt(taskSize - 1)));

			}

			for (int i = 0; i < taskSize / 2; i++) {

				children.addCandidate(wife.getCandidates().get(
						rand.nextInt(taskSize - 1)));

			}

		} else {
			// nieparzysta

			for (int i = 0; i < (taskSize + 1) / 2; i++) {
				children.addCandidate(husband.getCandidates().get(
						rand.nextInt(taskSize - 1)));
			}

			for (int i = 0; i < (taskSize - 1) / 2; i++) {
				children.addCandidate(wife.getCandidates().get(
						rand.nextInt(taskSize - 1)));
			}

		}
		LOGGER.debug("crossFunction finished...");
		return children;
	}

	public List<NewTask> crossTask(BestGene gene, NewPopulation population) {
		LOGGER.debug("crossTask started...");

		int taskSize = gene.getTasks().size();
		int crossTimes = population.getTasks().size() / taskSize;

		LOGGER.debug("taskSize" + taskSize);
		LOGGER.debug("crossTimes" + crossTimes);

		List<NewTask> taskAfterCrossing = new ArrayList<NewTask>();

		for (int i = 0; i < taskSize; i++) {
			int counter = 0;
			while (counter < crossTimes) {
				if (i == taskSize - 1) {

					taskAfterCrossing.add(crossFunction(gene.getTasks().get(i),
							gene.getTasks().get(0)));
				} else {

					taskAfterCrossing.add(crossFunction(gene.getTasks().get(i),
							gene.getTasks().get(i + 1)));
				}
				counter++;
			}

		}

		LOGGER.debug("taskAfterCrossing.size()" + taskAfterCrossing.size());
		LOGGER.debug("crossTask finished...");
		return taskAfterCrossing;

	}

	public void mixTask(NewTask task, int mixTimes) {
		LOGGER.debug("mixTask started...");
		int counter = 0;
		int position = 0;

		Random rand = new Random();
		List<NewPackage> taskList = task.getCandidates();

		while (counter < mixTimes) {

			position = rand.nextInt(taskList.size() - 1);

			task.changeCandidate(position, EM.getCandidate());

			counter++;

		}
		LOGGER.debug("mixTask finished...");
	}

	public Double getGoalFunctionForBestGene(NewPopulation bestGene) {
		return goalFunction(bestGene.getTasks().get(0));
	}

	public void setIterationToExcel(int iteration, NewPopulation bestGene,
			String filepath) {

		EM.setValues(iteration, getGoalFunctionForBestGene(bestGene), filepath);
	}

	public void closeFile() {
		EM.closeFile();
	}

}
